#include <core/types.h>
#include <core/io.h>
#include <core/irq.h>
#include <core/init.h>
#include <arch/arm.h>
#include <core/bitops.h>
#include <arch/s3c24xx.h>


#define EXTINT_OFF       (IRQ_EINT4 - 4)
#define INTMSK_TCnADC    (1UL << (IRQ_TC_ADC - IRQ_EINT0))

UINT32 ReadIrqNum(void)
{
	return ReadLong(S3C2410_INTOFFSET);
}

static void S3cIrqMask(UINT32 nIrq)
{
	unsigned long mask;

	mask = ReadLong(S3C2410_INTMSK);
	mask |= 1UL << (nIrq - IRQ_EINT0);
	WriteLong(mask, S3C2410_INTMSK);
}


static void S3cIrqAck(UINT32 nIrq)
{
	ULONG dwVal = 1UL << (nIrq - IRQ_EINT0);

	WriteLong(dwVal, S3C2410_SRCPND);
	WriteLong(dwVal, S3C2410_INTPND);
}


static void S3cIrqMaskAck(UINT32 nIrq)
{
	unsigned long dwVal = 1UL << (nIrq - IRQ_EINT0);
	unsigned long mask;

	mask = ReadLong(S3C2410_INTMSK);
	WriteLong(mask | dwVal, S3C2410_INTMSK);

	WriteLong(dwVal, S3C2410_SRCPND);
	WriteLong(dwVal, S3C2410_INTPND);
}


static void S3cIrqUnmask(UINT32 nIrq)
{
	unsigned long mask;

	nIrq -= IRQ_EINT0;

	mask = ReadLong(S3C2410_INTMSK);
	mask &= ~(1UL << nIrq);
	WriteLong(mask, S3C2410_INTMSK);
}


struct IntCtrl gS3cIntCtrlLevel =
{
	.Ack     = S3cIrqMaskAck,
	.Mask    = S3cIrqMask,
	.MaskAck = S3cIrqMaskAck,
	.Unmask  = S3cIrqUnmask,
};

struct IntCtrl gS3cIntCtrl =
{
	.Ack    = S3cIrqAck,
	.Mask   = S3cIrqMask,
	.Unmask = S3cIrqUnmask,
};

static void S3cExtMask(UINT32 nIrq)
{
	unsigned long mask;

	nIrq -= EXTINT_OFF;

	mask = ReadLong(S3C2410_EINTMASK);
	mask |= 1UL << nIrq;
	WriteLong(mask, S3C2410_EINTMASK);
}

static void S3cExtAck(UINT32 nIrq)
{
	unsigned long req;
	unsigned long bit;

	bit = 1UL << (nIrq - EXTINT_OFF);

	req = ReadLong(S3C2410_EINTPEND);

	WriteLong(req, S3C2410_EINTPEND);
	// WriteLong(bit, S3C2410_EINTPEND);

//	printf("%s(), line %d: req = 0x%08x\n", __FUNCTION__, __LINE__, req);

	// fixme
	if (nIrq <= IRQ_EINT7)
	{
		if ((req & 0xf0) != 0)
			S3cIrqAck(IRQ_EINT_4TO7);
	}
	else
	{
		if ((req >> 8) != 0)
		{
			S3cIrqAck(IRQ_EINT_8TO23);
		}
	}
}

static void S3cExtUnmask(UINT32 nIrq)
{
	unsigned long mask;

	nIrq -= EXTINT_OFF;

	mask = ReadLong(S3C2410_EINTMASK);
	mask &= ~( 1UL << nIrq);
	WriteLong(mask, S3C2410_EINTMASK);
}

int S3cExtSetTrigger(UINT32 nIrq, unsigned int type)
{
	ULONG extint_reg, gpcon_reg;
	unsigned long gpcon_offset, extint_offset;
	unsigned long newvalue = 0, value;

	if ((nIrq >= IRQ_EINT0) && (nIrq <= IRQ_EINT3))
	{
		gpcon_reg = GPIO_BASE + GPF_CON;
		extint_reg = GPIO_BASE + EXTINT0;
		gpcon_offset = (nIrq - IRQ_EINT0) * 2;
		extint_offset = (nIrq - IRQ_EINT0) * 4;
	}
	else if ((nIrq >= IRQ_EINT4) && (nIrq <= IRQ_EINT7))
	{
		gpcon_reg = GPIO_BASE + GPF_CON;
		extint_reg = GPIO_BASE + EXTINT0;
		gpcon_offset = (nIrq - (EXTINT_OFF)) * 2;
		extint_offset = (nIrq - (EXTINT_OFF)) * 4;
	}
	else if ((nIrq >= IRQ_EINT8) && (nIrq <= IRQ_EINT15))
	{
		gpcon_reg = GPIO_BASE + GPG_CON;
		extint_reg = GPIO_BASE + EXTINT1;
		gpcon_offset = (nIrq - IRQ_EINT8) * 2;
		extint_offset = (nIrq - IRQ_EINT8) * 4;
	}
	else if ((nIrq >= IRQ_EINT16) && (nIrq <= IRQ_EINT23))
	{
		gpcon_reg = GPIO_BASE + GPG_CON;
		extint_reg = GPIO_BASE + EXTINT2;
		gpcon_offset = (nIrq - IRQ_EINT8) * 2;
		extint_offset = (nIrq - IRQ_EINT16) * 4;
	} else
		return -1;

	value = ReadLong(gpcon_reg);
	value = (value & ~(3 << gpcon_offset)) | (0x02 << gpcon_offset);
	WriteLong(value, gpcon_reg);

	switch (type)
	{
		case IRQ_TYPE_NONE:
			printf("No edge setting!\n");
			break;

		case IRQ_TYPE_EDGE_RISING:
			newvalue = S3C2410_EXTINT_RISEEDGE;
			break;

		case IRQ_TYPE_EDGE_FALLING:
			newvalue = S3C2410_EXTINT_FALLEDGE;
			break;

		case IRQ_TYPE_EDGE_BOTH:
			newvalue = S3C2410_EXTINT_BOTHEDGE;
			break;

		case IRQ_TYPE_LEVEL_LOW:
			newvalue = S3C2410_EXTINT_LOWLEV;
			break;

		case IRQ_TYPE_LEVEL_HIGH:
			newvalue = S3C2410_EXTINT_HILEV;
			break;

		default:
			printf("No such nIrq type %d", type);
			return -1;
	}

	value = ReadLong(extint_reg);
	value = (value & ~(7 << extint_offset)) | (newvalue << extint_offset);
	WriteLong(value, extint_reg);

	return 0;
}

static struct IntCtrl gS3cIntCtrl4_23 = {
	.Ack		= S3cExtAck,
	.Mask		= S3cExtMask,
	.Unmask		= S3cExtUnmask,
	.SetTrigger	= S3cExtSetTrigger,
};

static struct IntCtrl gS3cIntCtrl0_3 = {
	.Ack		= S3cIrqAck,
	.Mask		= S3cIrqMask,
	.Unmask 	= S3cIrqUnmask,
	.SetTrigger	= S3cExtSetTrigger,
};


static inline void S3cSubMask(UINT32 nIrq, unsigned int parentbit, int subcheck)
{
	unsigned long mask;
	unsigned long submask;

	submask = ReadLong(S3C2410_INTSUBMSK);
	mask = ReadLong(S3C2410_INTMSK);

	submask |= (1UL << (nIrq - IRQ_UART0_RX));

	if ((submask  & subcheck) == subcheck)
	{
		WriteLong(mask | parentbit, S3C2410_INTMSK);
	}

	WriteLong(submask, S3C2410_INTSUBMSK);
}

static inline void S3cSubUnmask(UINT32 nIrq, unsigned int parentbit)
{
	unsigned long mask;
	unsigned long submask;

	submask = ReadLong(S3C2410_INTSUBMSK);
	mask = ReadLong(S3C2410_INTMSK);

	submask &= ~(1UL << (nIrq - IRQ_UART0_RX));
	mask &= ~parentbit;

	WriteLong(submask, S3C2410_INTSUBMSK);
	WriteLong(mask, S3C2410_INTMSK);
}


static inline void S3cSubMaskack(UINT32 nIrq, unsigned int parentmask, unsigned int group)
{
	unsigned int bit = 1UL << (nIrq - IRQ_UART0_RX);

	S3cSubMask(nIrq, parentmask, group);

	WriteLong(bit, S3C2410_SUBSRCPND);

	WriteLong(parentmask, S3C2410_SRCPND);
	WriteLong(parentmask, S3C2410_INTPND);
}

static inline void S3cSubAck(UINT32 nIrq, unsigned int parentmask, unsigned int group)
{
	unsigned int bit = 1UL << (nIrq - IRQ_UART0_RX);

	WriteLong(bit, S3C2410_SUBSRCPND);

	WriteLong(parentmask, S3C2410_SRCPND);
	WriteLong(parentmask, S3C2410_INTPND);
}


static void S3cAdcMask(UINT32 nIrq)
{
	S3cSubMask(nIrq, INTMSK_TCnADC, 3 << 9);
}

static void S3cAdcUnmask(UINT32 nIrq)
{
	S3cSubUnmask(nIrq, INTMSK_TCnADC);
}

static void S3cAdcAck(UINT32 nIrq)
{
	S3cSubAck(nIrq, INTMSK_TCnADC, 3 << 9);
}

static struct IntCtrl s3c_irq_adc = {
	.Mask		= S3cAdcMask,
	.Unmask		= S3cAdcUnmask,
	.Ack		= S3cAdcAck,
};

static void S3cIrqParseAdc(struct IrqDesc *pDesc, UINT32 nIrq)
{
	unsigned int nSubPnd;

	nSubPnd = ReadLong(S3C2410_SUBSRCPND);

	nSubPnd >>= 9;
	nSubPnd &= 3;

	if (nSubPnd & 1)
	{
		DoIRQ(IRQ_TC);
	}

	if (nSubPnd & 2)
	{
		DoIRQ(IRQ_ADC);
	}
}


static void S3cIrqParseExt(struct IrqDesc *pDesc, UINT32 nIrq)
{
	unsigned long nExtPnd = ReadLong(S3C2410_EINTPEND);

	
//	printf("%s(), line %d: nExtPnd = 0x%08x\n", __FUNCTION__, __LINE__, nExtPnd);
//	pDesc->pIntCtrl->Ack(nIrq);

	switch (nIrq)
	{
	case IRQ_EINT_4TO7:
		nExtPnd &= 0xf0;
		break;

	case IRQ_EINT_8TO23:
		nExtPnd &= ~0xff;
		break;

	default:
		break;
	}

	while (nExtPnd)
	{
		unsigned int nExtIrq;

		nExtIrq = ffs(nExtPnd) - 1;

		nExtPnd &= ~(1 << nExtIrq);
		nExtIrq += EXTINT_OFF;

//		printf("%s(), line %d: nExtIrq = 0x%08x\n", __FUNCTION__, __LINE__, nExtIrq);

		DoIRQ(nExtIrq);
	}
}


int __INIT__ S3c24xxInitIrq(void)
{
	int nIrq;


	for (nIrq = IRQ_EINT0; nIrq < MAX_IRQ_NUM; nIrq++)
	{
		switch (nIrq)
		{
		case IRQ_EINT0 ... IRQ_EINT3:
			IrqCtrlAssociate(nIrq, &gS3cIntCtrl0_3);
			IrqSetHandler(nIrq, IrqHandleEdge, 0);
			break;

		case IRQ_EINT4 ... IRQ_EINT23:
			IrqCtrlAssociate(nIrq, &gS3cIntCtrl4_23);
			IrqSetHandler(nIrq, IrqHandleEdge, 0);
			break;

		case IRQ_EINT_4TO7:
		case IRQ_EINT_8TO23:
			IrqCtrlAssociate(nIrq, &gS3cIntCtrlLevel);			
			IrqSetHandler(nIrq, S3cIrqParseExt, 1);
			break;

		case IRQ_UART0: // fixme
		case IRQ_UART1:
		case IRQ_UART2:
		case IRQ_TC_ADC:
			IrqCtrlAssociate(nIrq, &gS3cIntCtrlLevel);
			break;

		case IRQ_RESERVED6:
		case IRQ_RESERVED24:
			break;

		case IRQ_TC:
		case IRQ_ADC:
			IrqCtrlAssociate(nIrq, &s3c_irq_adc);
			IrqSetHandler(nIrq, IrqHandleEdge, 0);
			break;

		default:
			IrqCtrlAssociate(nIrq, &gS3cIntCtrl);
			IrqSetHandler(nIrq, IrqHandleEdge, 0);
		}
	}

	IrqSetHandler(IRQ_TC_ADC, S3cIrqParseAdc, 1);

	IrqEnable(); // fixme

	return 0;
}

